package com.gxa.eshop.portal.service.impl;


import com.gxa.eshop.core.exception.BusinessException;
import com.gxa.eshop.core.pojo.Goods;
import com.gxa.eshop.core.service.impl.BaseServiceImpl;
import com.gxa.eshop.core.utils.JsonUtils;
import com.gxa.eshop.portal.mapper.GoodsMapper;
import com.gxa.eshop.portal.service.GoodsService;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service("goodsService")
@Transactional(rollbackFor = Throwable.class)
public class GoodsServiceImpl extends BaseServiceImpl<Goods> implements GoodsService {

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    @Resource
    private GoodsMapper goodsMapper;

    // 指定商品key的前缀
    private static final String GOODS_CACHE="goods:";

    // 指定秒杀商品的key的前缀
    private static final String SECKILL_CACHE="seckill:";

    // 指定秒杀商品的锁的key的前缀
    private static final String LOCK_CACHE="lock:";

    // 指定过期时间常量
    private static final Integer GOODS_CACHE_EXPIRE=2;

    @Override
    public Goods queryGoodsByCacheId(Long id) throws BusinessException {
        // 确定操作类型为String, key的名字: GOODS_CACHE+id(goods:1)
        BoundValueOperations<String, Object> goodsOps = redisTemplate.boundValueOps(GOODS_CACHE + id);
        //1.先从缓存中找，如果有就直接返回
        Object jsonObject = goodsOps.get();      //  获取到的应该是json格式的商品信息


        //使用字符串相关的方法: isEmpty 判断字符串是否为空值/null
       /* public static boolean isEmpty(@Nullable Object str) {
            return str == null || "".equals(str);
        }*/
        boolean empty = StringUtils.isEmpty(jsonObject);
        if(empty){

            //2.如果没有就从数据库中查找，放入缓存，然后返回

            //只查询上架的商品(status)
            Goods goods = new Goods();
            goods.setId(id);
            goods.setStatus(1);

            Goods selectedGoods = goodsMapper.selectOne(goods);
            //  当商品在数据库不存在,就抛出业务异常
            if(selectedGoods == null){
                throw new BusinessException("没有该编号对应的商品");
            }
            //找到了做缓存
            //将商品对象转换为json
            //设置过期时间
            String objectParseString = JsonUtils.objectToJson(selectedGoods);
            goodsOps.set(objectParseString,GOODS_CACHE_EXPIRE, TimeUnit.HOURS);
            return selectedGoods;

        }else{
            // 使用JsonUtils的转换方法 , 把json字符串转换为Goods对象
            Goods goods = JsonUtils.jsonToObject(jsonObject.toString(), Goods.class);
            return goods;
        }
    }

    @Override
    public Boolean removeGoodsCache(Long id)  {
        Boolean delete = redisTemplate.delete(GOODS_CACHE + id);
//        if(!delete){
//            throw new BusinessException("删除失败, 请检查商品缓存的id是否存在,或者已经过期");
//        }
        return delete;
    }

    @Override
    public Boolean seckill(Long id) {
        // redis中先创建一组要被秒杀的商品(id)
        // set seckill:1 10    提供了10个被秒杀的商品(编号为1)

        // 利用redis的原子性操作, 执行seckill:1 的decr操作
        // 秒杀成功就-1

        // 为了保证同步, 是不能用synchronized , 因为同步锁只能用于当前服务器程序, 不能跨服务器的
        // 在多服务器存在的情况下, 实现分布式锁
        // NX 操作: 操作时必须是一个不存在的变量才能赋值成功
        // 于是准备一个key: lock:1
        // 先创建变量lock(加锁)->成功表示加锁完成-> 库存减少 -> 删除变量lock(解锁)

        // 还有可能出现的情况:
        // 如果用户在15内没有完成秒杀, 但此时锁已经释放了, 第二个人又可以加锁, 第三个人获取锁的发现,锁已经被第一个人释放了
        //  要想解决, 就可以通过这样的方式 : 锁只能由创建的人来释放
        // 给锁赋值为当前用户才知道的值: UUID

        // 加锁
        ValueOperations<String, Object> stringOps = redisTemplate.opsForValue();
        // stringOps.setIfAbsent(LOK_CACHE+id,true);

        // 为了保证锁一定会删除, 还可以加有效时间: 15秒钟
        // Boolean locked = stringOps.setIfAbsent(LOK_CACHE + id, true, 15, TimeUnit.SECONDS);// 原子级别操作
        String lockValue = UUID.randomUUID().toString();
        Boolean locked = stringOps.setIfAbsent(LOCK_CACHE + id, lockValue, 15, TimeUnit.SECONDS);// 原子级别操作

        // 如果返回false, 表示锁已经存在,此次秒杀失败
        if(!locked){
            return false;
        }

        // 获取seckill:1 的值
        BoundValueOperations<String, Object> boundValueOps = redisTemplate.boundValueOps(SECKILL_CACHE + id);
        // 获取秒杀商品的数量nums: 看做是库存
        Integer nums = Integer.parseInt(boundValueOps.get().toString());

//        当库存>0, 秒杀才能成功执行
        if(nums>0){
            // 每次库存-1
            //boundValueOps.decrement(1);
            boundValueOps.decrement();
            System.out.println("秒杀成功,库存的数量是:"+(--nums));

            //释放锁
            // 增加一个判断, 当前锁的值和UUID相同, 表示是用户本人加的锁, 才能释放
            if(lockValue.equals(stringOps.get(LOCK_CACHE+id))){
                Boolean delete = redisTemplate.delete(LOCK_CACHE + id);
                // 在Redisson操作可以进一步保证分布式锁的稳定,甚至延迟锁的时间
            }
            return true;
        }
        return false;
    }
}